package com.gitee.wsl.struct.grammar

import assertk.all
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
import assertk.assertions.prop
import com.gitee.wsl.struct.grammar.parser.Parser
import com.gitee.wsl.struct.grammar.parser.parser
import com.gitee.wsl.struct.grammar.parser.combinator.SkipParser
import com.gitee.wsl.base.Tuple2
import com.gitee.wsl.base.Tuple3
import com.gitee.wsl.struct.grammar.parser.combinator.TupleParser
import com.gitee.wsl.struct.grammar.parser.combinator.and
import com.gitee.wsl.struct.grammar.parser.combinator.times
import com.gitee.wsl.struct.grammar.parser.combinator.unaryMinus
import com.gitee.wsl.struct.grammar.parser.token.LiteralToken
import com.gitee.wsl.struct.grammar.parser.token.TokenMatch
import com.gitee.wsl.struct.grammar.parser.token.literalToken
import kotlin.reflect.KClass
import kotlin.test.Test

@Suppress("RemoveExplicitTypeArguments")
class TupleCombinatorsTest {

    private val tupleParserClass = TupleParser::class
    private val skipParserClass = SkipParser::class
    private val literalTokenClass = LiteralToken::class

    private val ga = object : Grammar<TokenMatch>() {
        val a by literalToken("a")
        override val root by Parser.parser { a() }
    }

    @Test
    fun andTests() {
        val a = ga.a

        check(
            parser = a and a,
            arity = 2,
            parserCount = 2,
            parserClasses = listOf(literalTokenClass, literalTokenClass),
            parses = "aa",
        )

        check(
            parser = a and a and a,
            arity = 3,
            parserCount = 3,
            parserClasses = listOf(literalTokenClass, literalTokenClass, literalTokenClass),
            parses = "aaa",
        )

        check<TokenMatch>(
            parser = a and -a,
            arity = 1,
            parserCount = 2,
            parserClasses = listOf(literalTokenClass, skipParserClass),
            parses = "a",
        )

        check<TokenMatch>(
            parser = -a and a,
            arity = 1,
            parserCount = 2,
            parserClasses = listOf(skipParserClass, literalTokenClass),
            parses = "a",
        )

        check<Unit>(
            parser = -a and -a,
            arity = 0,
            parserCount = 2,
            parserClasses = listOf(skipParserClass, skipParserClass),
            parses = "",
        )

        check<Tuple2<TokenMatch, TokenMatch>>(
            parser = a and a and -a,
            arity = 2,
            parserCount = 3,
            parserClasses = listOf(literalTokenClass, literalTokenClass, skipParserClass),
            parses = "aa",
        )

        check<Tuple3<TokenMatch, TokenMatch, TokenMatch>>(
            parser = a and a and -a and a,
            arity = 3,
            parserCount = 4,
            parserClasses = listOf(literalTokenClass, literalTokenClass, skipParserClass, literalTokenClass),
            parses = "aaa",
        )

        check<Tuple3<TokenMatch, TokenMatch, Tuple2<TokenMatch, TokenMatch>>>(
            parser = -a and (a and -a and a) and (-a and -a) and (a and (a and -a)),
            arity = 2,
            parserCount = 4,
            parserClasses = listOf(
                skipParserClass, tupleParserClass, skipParserClass, tupleParserClass
            ),
            parses = "aaaa",
        )
    }

    @Test
    fun timesTests() {
        val a = ga.a

        check<Tuple3<TokenMatch, TokenMatch, Tuple2<TokenMatch, TokenMatch>>>(
            parser = -a * (a * -a * a) * (-a * -a) * (a * (a * -a)),
            arity = 2,
            parserCount = 4,
            parserClasses = listOf(
                skipParserClass, tupleParserClass, skipParserClass, tupleParserClass
            ),
            parses = "aaaa",
        )
    }

    private fun <T> check(
        parser: Parser<T>,
        arity: Int,
        parserCount: Int,
        parserClasses: List<KClass<*>>,
        parses: String? = null
    ) {
        assertThat(parser).isInstanceOf<TupleParser<*>>().all {
            prop(TupleParser<*>::arity).isEqualTo(arity)
            prop(TupleParser<*>::parsers).all {
                prop(List<*>::size).isEqualTo(parserCount)
                prop("classes") { list -> list.map { it::class } }.isEqualTo(parserClasses)
            }
        }

        if (parses != null) {
            ga.parse(parser, parses)
        }
    }

}
